<?php


namespace App\Services;

use App\Models\Drivers;
use App\Models\User;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Illuminate\Support\Facades\Log;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;

class WebSocketService implements WebSocketHandlerInterface
{

    /**@var \Swoole\Table $wsTable */
    private $wsTable;
    private $redis;
    private $order;
    private $key1 = 'all_online_drivers';
    private $key2 = 'all_online_users';

    // 声明构造函数
    public function __construct()
    {
        $this->redis = new RedisOperation; //实例化redis
        $this->wsTable = app('swoole')->wsTable;
        $this->order = new  DriversOrderService; //实例化订单模型
    }

    public function sendJson($code, $msg, $data = [], $event = '')
    {
        $data = [
            'code' => $code,
            'msg' => $msg ? $msg : 'ok',
            'data' => $data,
            'event' => $event,
        ];
        return json_encode($data);
    }

    public function onOpen(Server $server, Request $request)
    {
        //  \Illuminate\Support\Facades\Cache::decrement('all_online_count');
        // var_dump(app('swoole') === $server);// 同一实例
        /**
         * 获取当前登录的用户
         * 此特性要求建立WebSocket连接的路径要经过Authenticate之类的中间件。
         * 例如：
         * 浏览器端：var ws = new WebSocket("ws://127.0.0.1:5200/ws");
         * 那么Laravel中/ws路由就需要加上类似Authenticate的中间件。
         */
        //$user = Auth::user();
        $token = $request->header['token'] ? $request->header['token'] : '';
        $user = User::query()->where('token', $token)->first('id');
        $userId = $user ? $user->id : 0;
        if ($userId != 0) {
            $drivers_id = Drivers::query()->where('user_id', $userId)->first();
            if ($drivers_id) {
                //  $drivers = Cache::get($this->key1, []);
                //$drivers = array_push($drivers, $drivers_id->id);
                // Cache::forever($this->key1, $drivers);
                $drivers_id = $drivers_id->id;
            } else {
                // $users = Cache::get($this->key2, []);
                //    $users = array_push($users, $userId);
                //  Cache::forever($this->key2, $users);
                $drivers_id = 0;
            }
            $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// 绑定fd到$drivers_id的映射
            $this->wsTable->set('dfd:' . $request->fd, ['value' => $drivers_id]);// 绑定fd到$drivers_id的映射
            //  $this->wsTable->set('drivers_id:' . $drivers_id, ['value' => $request->fd]);// 绑定drivers_id到映射fd的映射
            $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// 绑定$user_id到fd的映射
            $server->push($request->fd, $this->sendJson(1, '连接成功', $userId));
        } else {
            // 未登录用户直接断开连接
            // $server->disconnect($request->fd);
            return $server->push($request->fd, $this->sendJson(-1, '连接失败', $userId, 'login'));
        }
    }

    /**
     * @param Server $server
     * @param Frame $frame
     * @return mixed
     */
    public function onMessage(Server $server, Frame $frame)
    {
        $data = json_decode($frame->data, true);
        switch ($data['type']) {
            case "driver":
                switch ($data['event']) {
                    case 'xt':
                        $server->push($frame->fd, $this->sendJson(1, '', [], 'xt'));//发送当前用户
                        break;
                    //初始化司机信息
                    case 'location':
                        $this->loaction($data['data']);
                        $server->push($frame->fd, $this->sendJson(1, '', [], 'location'));//发送当前用户
                        break;
                    //接单
                    case 'receiving':
                        $res = $this->ConfirmReceipt($data['data']);
                        if ($res == 0) return $server->push($frame->fd, $this->sendJson(0, '该订单已被其他司机接单', $data['data'], 'receiving'));//向司机推送抢单失败
                        $driver = new  DriversPostage();
                        $type = $this->order_type((int)$data['data']['order_id']); //获取订单类型
                        if ($type == '0') {
                            $info = $driver->DriversEstimatedCosts((int)$data['data']['order_id'], $res);
                        } else {
                            $info = $driver->EstimatedCost((int)$data['data']['order_id']);
                        }
                        $server->push($frame->fd, $this->sendJson(1, '接单成功', $info, 'receiving'));//向司机推送抢单成功
                        $datas = $this->driver_info($data['data']);
                        //向乘客推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->user_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->user_id($data['data']))['value'], $this->sendJson(1, '司机正在前往上车地点', $datas, 'receiving'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '向乘客退单失败', $info, 'receiving'));//向司机推送抢单成功
                        }
                        break;
                    //到达上车地点
                    case 'boarding':
                        $res = $this->boardings($data['data']);
                        if ($res == 0) {
                            return $server->push($frame->fd, $this->sendJson(0, '到达上车点操作失败', '', 'boarding'));//到达上车点点击无效
                        }
                        //向司机推送订单
                        $server->push($frame->fd, $this->sendJson(1, '到达上车点', $res, 'boarding'));//向司机推送到达上车点
                        //向乘客推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->user_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->user_id($data['data']))['value'], $this->sendJson(1, '司机已经到指定位置的请您尽快前往上车点', $res, 'boarding'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '向乘客退单失败', '', 'boarding'));//向司机推送抢单成功
                        }
                        break;
                    //接到乘客
                    case 'to_passenger':
                        $res = $this->to_passengerc($data['data']);
                        if ($res <> 1) {
                            return $server->push($frame->fd, $this->sendJson(0, '接到乘客操作失败', $res, 'to_passenger'));//接到乘客点击失败
                        }
                        //向司机推送订单
                        $server->push($frame->fd, $this->sendJson(1, '您已成功接到乘客请确认手机尾号', '', 'to_passenger'));//您已成功接到乘客
                        $datas = $this->driver_info($data['data']);
                        //向乘客推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->user_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->user_id($data['data']))['value'], $this->sendJson(1, '司机正在服务中', $datas, 'to_passenger'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '向乘客退单失败', $datas, 'to_passenger'));//向司机推送抢单成功
                        }
                        break;
                    //到达目的地
                    case 'terminus':
                        $res = $this->terminus($data['data']);
                        if ($res <> 1) {
                            return $server->push($frame->fd, $this->sendJson(0, '确认到达目的地操作失败', $res, 'terminus'));//确认到达目的地点击无效
                        }
                        //向司机推送订单
                        $server->push($frame->fd, $this->sendJson(1, '确认到达目的地点操作成功', $data['data'], 'terminus'));//确认到达目的地点操作成功并向当前司机推送成功的消息
                        //向乘客推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->user_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->user_id($data['data']))['value'], $this->sendJson(1, '确认到达目的地点操作成功', $data['data'], 'terminus'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '确认到达目的地点操作成功', $data['data'], 'terminus'));//向司机推送抢单成功
                        }
                        break;
                    //取消订单
                    case 'refund_order':
                        try {
                            $res = $this->refund_order($data['data']);  //司机取消订单
                        } catch (\Exception $exception) {
                            $res = 0;
                        }
                        if ($res <> 1) {
                            return $server->push($frame->fd, $this->sendJson(0, '取消订单操作无效', $res, 'refund_order'));//取消订单操作无效
                        }
                        //向司机推送订单
                        $server->push($frame->fd, $this->sendJson(1, '取消成功', $data['data'], 'refund_order'));//确认到达目的地点操作成功并向当前司机推送成功的消息
                        //向乘客推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->user_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->user_id($data['data']))['value'], $this->sendJson(1, '司机取消订单', $data['data'], 'refund_order'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '司机取消订单', $data['data'], 'refund_order'));//向司机推送抢单成功
                        }
                }
                break;
            case 'passenger':
                switch ($data['event']) {
                    //心跳
                    case "xt":
                        $server->push($frame->fd, $this->sendJson(1, '', 'xt', 'xt'));//心跳检测
                        break;
                    //乘客下单成功后向司机推送订单
                    case 'order':
                        $this->order($server, $data['data'], $frame->fd);
                        break;
                    //获取司机轨迹
                    case 'driver_track':
                        $this->passenger_order_status($server, $data['data'], $frame->fd); //向乘客推送信息
                        break;
                    //取消订单
                    case 'passenger_cancellation':
                        if ($this->redis->exists('driver' . $data['data']['order_id'])) {
                            $this->redis->del('driver' . $data['data']['order_id']); //删除轨迹
                        }
                        $server->push($frame->fd, $this->sendJson(1, '', '', 'passenger_cancellation'));//取消成功
                        //向司机推送订单
                        try {
                            if ($this->wsTable->get('uid:' . $this->driver_id($data['data'])) !== false)
                                $server->push($this->wsTable->get('uid:' . $this->driver_id($data['data']))['value'], $this->sendJson(1, '乘客端取消订单成功', $data['data'], 'passenger_cancellation'));//向乘客推送司机到达上车点
                        } catch (\Exception $exception) {
                            $server->push($frame->fd, $this->sendJson(0, '乘客端取消订单成功', $data['data'], 'passenger_cancellation'));//向司机推送抢单成功
                        }
                        break;
                }
                break;
        }
    }

    public function getValue($key)
    {
        $value = 0;
        try {
            if ($this->wsTable->exists($key)) {
                $res = $this->wsTable->get($key);
                if ($res !== false) {
                    $value = $res['value'];
                }
            }
        } catch (\Exception $exception) {
            Log::info('websocket', $exception);
        }
        return $value;
    }

    /**
     * 司机取消订单
     */
    public function refund_order($data)
    {
        return $this->order->cancellation_of_order((int)$data['order_id']);
    }

    /**
     * 当前订单实时监测
     */
    private function passenger_order_status(Server $server, $data, $fd)
    {
        if (!empty($data['order_id'])) {
            $info = $this->order->driver_info($data);
            if (!$info) return $server->push($fd, $this->sendJson(0, '订单不存在', '', 'order'));
            switch ($info['status']) {
                case '1':
                    $server->push($fd, $this->sendJson(1, '发单成功等待司机接单中', '', 'order'));
                    break;
                case '2':
                    if ($info['reality_start'] == null || $info['reality_start'] == '') {
                        return $server->push($fd, $this->sendJson(1, '司机正在前往上车地点', $info, 'receiving'));
                    } else {
                        return $server->push($fd, $this->sendJson(1, '司机已到达上车点请尽快前往指定地点', $info['arrive_time'], 'boarding'));
                    }
                    break;
                case '3':
                    if ($info['end_time'] == '') {
                        $server->push($fd, $this->sendJson(1, '司机正在前往上车地点', $info, 'to_passenger'));
                    } else {
                        $server->push($fd, $this->sendJson(1, '司机已到达目的地', '', 'terminus'));
                    }
                    break;
                case '4':
                    $server->push($fd, $this->sendJson(1, '司机发起收款', '', 'passenger_terminus'));
                    break;
                case '-2':
                    $server->push($fd, $this->sendJson(1, '司机取消订单', '', 'refund_order'));
                    break;
            }
        }
    }

    /**
     * 司机到达上车点
     */
    private function boardings($data)
    {
        return $this->order->boardings($data);
    }

    /**
     * 司机接单成功后返回司机相关的信息
     */
    private function driver_info($data)
    {

        return $this->order->driver_info($data);
    }

    /**
     * @param int $order_id
     * 司机定位
     */
    private function driver_id($data)
    {

        $user = $this->order->driver_ids((int)$data['order_id']);
        if ($user) {
            return $user->user_id;
        }
        return false;
    }

    /**
     * 确认到达目的地
     */
    private function terminus($data)
    {
        return $this->order->terminus($data);
    }

    /**
     *确认接到乘客
     */
    private function to_passengerc($data)
    {

        return $this->order->to_passengerc($data['order_id']);
    }

    private function user_id($data)
    {
        return $this->order->user_id($data);
    }

    /**
     * 司机接单
     */
    private function ConfirmReceipt($data)
    {
        $reselut = $this->order->ConfirmReceipt($data['order_id'], $data['driver_id'], $data['car_id']);
        return $reselut;
    }

    /**
     * @param Server $server
     * @param $data
     * @param $fd
     * @return DriversOrderService|\Illuminate\Database\Eloquent\Model|mixed|null|object
     */
    public function order(Server $server, $data, $fd)
    {
        $arr = $this->order->order_info((int)$data['order_id']); //经纬度
        if ($arr) {
            if ($arr['type'] == '0') {
                $this->orders($server, $data, $arr); //网约车
            } else {
                $this->driving($server, $data, $arr); //代驾
            }
        }
        //订单状态查询
        return $this->order_status($server, $data, $fd);
    }

    //代驾推单逻辑
    protected function driving(Server $server, $data, $arr)
    {
        $lat_lng = $arr['lat'];
        $driver = new  DriversPostage();
        foreach ($this->wsTable as $key => $row) {
            if (strpos($key, 'fd:') === 0 && $server->isEstablished(substr($key, 3))) {
                $driver_id = $this->wsTable->get('d' . $key);
                if ($driver_id['value'] == 0) continue;
                if (!$type = $this->redis->exists('type' . $driver_id['value'])) continue;
                if ($type != '1') continue; //判断是不是代驾接单状态
                $ss = $this->redis->get('driver' . $driver_id['value']);
                if (!$ss) continue;
                $ss = explode(',', $ss);
                $juli = self::getDistance($ss['1'], $ss['0'], $lat_lng[0], $lat_lng[1], 2); //计算司机与订单的距离
                $info = $driver->EstimatedCostInt((int)$data['order_id']); //司机预计费用计算
                Log::info($info);//打印日志
                //派单就近10公里司机
                $julis = self::SendOrderPatternServers((int)$data['server_type']); //派单范围
                if ($julis == false) continue;
                if ($juli <= $julis) {
                    //查询司机是否有未完成订单
                    $re = self::order_statuss($driver_id['value']);
                    if (!$re) {
                        if ($this->drivers_user_id($driver_id['value']) !== false)
                            $value = self::getValue('uid:' . $this->drivers_user_id($driver_id['value']));
                        $info['distance'] = $juli;
                        if ($value != 0) $server->push($value, $this->sendJson(1, '发单成功等待司机接单', $info, 'order'));//向就近司机推单1

                    }
                }
            }
        }
    }

    /**
     * 快车推单
     */
    protected function orders(Server $server, $data, $arr)
    {
        $lat_lng = $arr['lat'];
        $driver = new  DriversPostage();
        foreach ($this->wsTable as $key => $row) {
            if (strpos($key, 'fd:') === 0 && $server->isEstablished(substr($key, 3))) {
                $driver_id = $this->wsTable->get('d' . $key);
                if ($driver_id['value'] == 0) continue;
                // if (!$type = $this->redis->exists('type' . $driver_id['value'])) continue;
                // if ($type != 0 || $type=='1') continue; //判断是不是快车接单状态
                $ss = $this->redis->get('driver' . $driver_id['value']);
                if (!$ss) continue;
                $ss = explode(',', $ss);
                $juli = self::getDistance($ss['1'], $ss['0'], $lat_lng[0], $lat_lng[1], 2); //计算司机与订单的距离
                Log::info($juli);//打印日志
                $type = (string)self::type((int)$driver_id['value']); //获取司机业务类型
                $info = $driver->DriversEstimatedCosts((int)$data['order_id'], (int)$type); //司机预计费用计算
                //派单就近5公里司机
                $julis = self::SendOrderPatternServers((int)$type); //派单范围
                if ($julis == false) continue;
                if ($juli <= $julis) {
                    //派单业务是否与司机车辆业务匹配
                    if (empty($data['server_type'])) continue;
                    if (strpos($data['server_type'], $type) !== false) {
                        //查询司机是否有未完成订单
                        $re = self::order_statuss($driver_id['value']);
                        if (!$re) {
                            if ($this->drivers_user_id($driver_id['value']) !== false)
                                $value = self::getValue('uid:' . $this->drivers_user_id($driver_id['value']));
                            $info['distance'] = $juli;
                            //  $server->push(substr($key, 3), $this->sendJson(1, '发单成功等待司机接单', $info, 'order'));//向就近司机推单1
                            if ($value != 0) $server->push($value, $this->sendJson(1, '发单成功等待司机接单', $info, 'order'));//向就近司机推单1

                        }
                    }
                }
            }
        }
    }

    protected function order_statuss($driver_id)
    {
        $type = $this->order->order_statuss($driver_id);
        return $type;
    }

    /**
     * 司机业务类型获取
     */
    protected function type(int $driver_id)
    {
        $type = $this->order->driver_type($driver_id);
        return $type;
    }

    /**
     * @param $order_id
     * @return DriversOrderService|\Illuminate\Database\Eloquent\Model|null|object
     * 订单状态查询
     */
    private function order_status(Server $server, $data, $fd)
    {
        $info = $this->order->driver_info($data);
        switch ($info['status']) {
            case '1':
                $server->push($fd, $this->sendJson(1, '发单成功等待司机接单中', '', 'order'));
                break;
            case '2':
                if ($info['arrive_time'] == '') {
                    $server->push($fd, $this->sendJson(1, '司机正在前往上车地点', $info, 'receiving'));
                } else {
                    $server->push($fd, $this->sendJson(1, '司机正在前往上车地点', $info['arrive_time'], 'boarding'));
                }
                break;
            case '3':
                if ($info['end_time'] == '') {
                    $server->push($fd, $this->sendJson(1, '司机正在前往上车地点', $info, 'to_passenger'));
                } else {
                    if ($this->wsTable->get('uid:' . $this->user_id($data))['value'] !== false)
                        $server->push($fd, $this->sendJson(1, '司机已到达目的地', '', 'terminus'));
                }
                break;
            case '4':
                $server->push($fd, $this->sendJson(1, '等待支付', '', 'passenger_terminus'));
                break;
            case '-2':
                $server->push($fd, $this->sendJson(1, '司机取消订单', '', 'refund_order'));
                break;
            case '-1':
                $server->push($fd, $this->sendJson(1, '乘客取消订单', '', 'passenger_cancellation'));
                break;
        }
    }

    //司机轨迹
    public function loaction($data)
    {

        $this->redis->set('driver' . $data['driver_id'], $data['lat'] . ',' . $data['lng']);
        if (!empty($data['order_id'])) {
            $lat = $data['lat'] . ',' . $data['lng'];
            $this->redis->rpush('driver' . $data['order_id'], $lat); //存储金纬度轨迹
        }
        if (!empty($data['car_id'])) {
            $this->redis->set('car' . $data['driver_id'], $data['car_id']);
        } //存储司机出车车辆ID
        $type = $data['type'] ? $data['type'] : 0;
        $this->redis->set('type' . $data['driver_id'], $type); //司机当前类型  默认快车类型
    }

    /**
     * 获取司机ID
     */
    private function drivers_in_id(int $uid)
    {
        return $this->order->drivers_in_id($uid);
    }

    /**
     * 获取用户ID
     */
    private function drivers_user_id($driver_id)
    {
        $drivers = $this->order->drivers_user_id($driver_id);
        return $drivers ? $drivers->user_id : false;
    }

    /**
     * 计算两点地理坐标之间的距离
     * @param Decimal $longitude1 起点经度
     * @param Decimal $latitude1 起点纬度
     * @param Decimal $longitude2 终点经度
     * @param Decimal $latitude2 终点纬度
     * @param Int $unit 单位 1:米 2:公里
     * @param Int $decimal 精度 保留小数位数
     * @return Decimal
     */
    function getDistance($longitude1, $latitude1, $longitude2, $latitude2, $unit = 2, $decimal = 2)
    {

        $EARTH_RADIUS = 6370.996; // 地球半径系数
        $PI = 3.1415926;

        $radLat1 = $latitude1 * $PI / 180.0;
        $radLat2 = $latitude2 * $PI / 180.0;

        $radLng1 = $longitude1 * $PI / 180.0;
        $radLng2 = $longitude2 * $PI / 180.0;

        $a = $radLat1 - $radLat2;
        $b = $radLng1 - $radLng2;

        $distance = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2)));
        $distance = $distance * $EARTH_RADIUS * 1000;

        if ($unit == 2) {
            $distance = $distance / 1000;
        }

        return round($distance, $decimal);

    }

    /**
     * 订单类型获取
     */
    private function order_type(int $order_id)
    {
        $type = $this->order->order_type($order_id);
        return $type;
    }

    /**
     * 距离
     */
    private function SendOrderPatternServers($service_type_id)
    {
        $server = new SendOrderPatternServers();
        $id = $server->get_find($service_type_id);
        if ($id) {
            return $id->distance;
        }
        return false;
    }

    /**
     * @param Server $server
     * @param $fd
     * @param $reactorId
     * 断开连接
     */
    public function onClose(Server $server, $fd, $reactorId)
    {
        //  \Illuminate\Support\Facades\Cache::decrement('all_online_count');
        $uid = $this->wsTable->get('fd:' . $fd);
        if ($uid !== false) {
            $this->wsTable->del('uid:' . $uid['value']); // 解绑uid映射
            $driver= $this->drivers_in_id((int)$uid['value']); //查询是否有司机ID
            if (!empty($driver)) {
                //删除司机坐标缓存
                if ($this->redis->exists('driver' . $driver->id)) {
                    $this->redis->del('driver' . $driver->id);
                    $this->redis->del('type' . $driver->id); //司机当前类型  默认快车类型
                }
                //删除司机车辆缓存
                if ($this->redis->exists('car' . $driver->id)) {
                    $this->redis->del('car' . $driver->id);
                }
            }
        }
        $this->wsTable->del('fd:' . $fd);// 解绑fd映射
        $server->push($fd, $this->sendJson(1, 'Goodbye', $fd, 'Goodbye'));

    }
}
